package com.umeox.moto.mqtt.send;

import android.content.Context;
import android.text.TextUtils;
import android.util.Log;

import com.umeox.moto.mqtt.BadNetworkException;
import com.umeox.moto.mqtt.BuildConfig;
import com.umeox.moto.mqtt.MqttDefaultCallback;
import com.umeox.moto.mqtt.MqttNet;
import com.umeox.moto.mqtt.Utils;
import com.umeox.moto.mqtt.base.ASend;
import com.umeox.moto.mqtt.base.PersistentConnectState;
import com.umeox.moto.mqtt.event.ISendExecutor;
import com.umeox.moto.mqtt.request.MqttPublishRequest;
import com.umeox.moto.mqtt.request.MqttSubscribeRequest;

import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.nio.charset.StandardCharsets;

public class MqttSendExecutor implements ISendExecutor {

    private MqttSendExecutor() {
    }

    public static MqttSendExecutor createExecutor() {
        return new MqttSendExecutor();
    }

    @Override
    public void asyncSend(ASend send) {
        if (send != null && send.getRequest() != null) {
            MqttAsyncClient asyncClient = MqttNet.getInstance().getMqttClient();
            if (asyncClient == null) {
                if (BuildConfig.DEBUG) {
                    Log.e("MqttSendExecutor", "asyncSend(): MqttNet::getClient() return null");
                }
                return;
            }

            if (!(send instanceof MqttSend)) {
                if (BuildConfig.DEBUG) {
                    Log.d("MqttSendExecutor", "asyncSend(): bad parameter: need MqttSend");
                }
                return;
            }

            MqttSend mqttSend = (MqttSend) send;
            Context mContext = MqttNet.getInstance().getContext();
            if (mContext != null && !Utils.isAvailable(mContext)) {
                if (BuildConfig.DEBUG) {
                    Log.d("MqttSendExecutor", "asyncSend(): bad Network");
                }
                mqttSend.setStatus(MqttSendStatus.COMPLETED);
                mqttSend.onFailure(null, new BadNetworkException());
                return;
            }

            if (MqttNet.getInstance().getConnectState() != PersistentConnectState.CONNECTED) {
                if (BuildConfig.DEBUG) {
                    Log.d("MqttSendExecutor", "asyncSend(): gateway disconnect");
                }
                mqttSend.setStatus(MqttSendStatus.COMPLETED);
                mqttSend.onFailure(null, new BadNetworkException());
                return;
            }

            if (send.getRequest() instanceof MqttPublishRequest) {
                publishRequest(asyncClient, mqttSend);
            } else if (send.getRequest() instanceof MqttSubscribeRequest) {
                subscribeRequest(asyncClient, mqttSend);
            }
        } else {
            if (BuildConfig.DEBUG) {
                Log.e("MqttSendExecutor", "asyncSend(): bad parameters: NULL");
            }
        }
    }

    private void publishRequest(MqttAsyncClient asyncClient, MqttSend mqttSend) {
        MqttPublishRequest publishRequest = (MqttPublishRequest) mqttSend.getRequest();
        if (TextUtils.isEmpty(publishRequest.topic) || publishRequest.payloadObj == null) {
            if (BuildConfig.DEBUG) {
                Log.e("MqttSendExecutor", "asyncSend(): bad parameters: topic or payload empty");
            }
            mqttSend.onFailure(null, new NullPointerException("topic or payload empty"));
            return;
        }
        try {
            byte[] payload = null;
            if (publishRequest.payloadObj instanceof String) {
                payload = publishRequest.payloadObj.toString().getBytes(StandardCharsets.UTF_8);
            } else if (publishRequest.payloadObj instanceof byte[]) {
                payload = (byte[]) publishRequest.payloadObj;
            } else {
                try {
                    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                    ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
                    outputStream.writeObject(publishRequest.payloadObj);
                    outputStream.flush();
                    payload = byteArrayOutputStream.toByteArray();
                } catch (Exception e) {
                    if (BuildConfig.DEBUG) {
                        Log.e("MqttSendExecutor", "asyncSend(): convert payload Obj to byte array error");
                        e.printStackTrace();
                    }
                }
            }

            if (publishRequest.payloadObj == null) {
                if (BuildConfig.DEBUG) {
                    Log.d("MqttSendExecutor", "asyncSend(): payload is empty");
                }
                mqttSend.onFailure(null, new NullPointerException("payload empty"));
                return;
            }
            if (BuildConfig.DEBUG) {
                Log.d("MqttSendExecutor", "publish: topic: [ " + publishRequest.topic + " ]" + ",payload: [ " + publishRequest.payloadObj + " ],QOS = " + publishRequest.qos);
            }
            MqttMessage message = new MqttMessage(payload);
            message.setQos(publishRequest.qos);
            if (publishRequest.isRPC) {
                mqttSend.setStatus(MqttSendStatus.WAITING_TO_PUBLISH);
            } else {
                mqttSend.setStatus(MqttSendStatus.WAITING_TO_COMPLETE);
            }
            //$sys/thing/ping
            if (!publishRequest.topic.equals("$sys/thing/ping")
                    && !publishRequest.topic.startsWith("$sys/rpc/cloud/post_reply/")
                    && !publishRequest.topic.startsWith("$sys/cloud/push_reply/")
                    && !("requestFriend".equalsIgnoreCase(getMethod(publishRequest)))
                    && !("sendChatMsg".equalsIgnoreCase(getMethod(publishRequest)))) { //PING 服务器不会回复 和 设备回复云端主题
                asyncClient.setCallback(mqttSend);
            }
            asyncClient.publish(publishRequest.topic, message, null, mqttSend);
        } catch (Exception e) {
            if (BuildConfig.DEBUG) {
                Log.d("MqttSendExecutor", "asyncSend(), send publish error, e = " + e.toString());
            }
            mqttSend.setStatus(MqttSendStatus.COMPLETED);
            mqttSend.onFailure(null, new MqttThrowable(e.getMessage()));
            asyncClient.setCallback(new MqttDefaultCallback());
        }
    }

    private void subscribeRequest(MqttAsyncClient asyncClient, MqttSend mqttSend) {
        MqttSubscribeRequest request = (MqttSubscribeRequest) mqttSend.getRequest();
        if (TextUtils.isEmpty(request.topic)) {
            if (BuildConfig.DEBUG) {
                Log.e("MqttSendExecutor", "asyncSend(): bad parameters: subsribe req , topic empty");
            }
            mqttSend.onFailure(null, new NullPointerException("subsribe req , topic empty"));
            return;
        }
        try {
            mqttSend.setStatus(MqttSendStatus.WAITING_TO_COMPLETE);
            if (request.isSubscribe) {
                if (BuildConfig.DEBUG) {
                    Log.d("MqttSendExecutor", "subscribe: topic: [ " + request.topic + " ]");
                }
                asyncClient.subscribe(request.topic, 0, null, mqttSend);
            } else {
                if (BuildConfig.DEBUG) {
                    Log.d("MqttSendExecutor", "unsubscribe: topic: [ " + request.topic + " ]");
                }
                asyncClient.unsubscribe(request.topic, null, mqttSend);
            }
        } catch (Exception e) {
            if (BuildConfig.DEBUG) {
                Log.d("MqttSendExecutor", "asyncSend(), send subsribe error, e = " + e.toString());
            }
            mqttSend.setStatus(MqttSendStatus.COMPLETED);
            mqttSend.onFailure(null, new MqttThrowable(e.getMessage()));
        }
    }

    private String getMethod(MqttPublishRequest publishRequest) {
        try {
            JSONObject jsonObject = new JSONObject(String.valueOf(publishRequest.payloadObj));
            if (jsonObject.has("method")) {
                return (String) jsonObject.get("method");
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return null;
    }
}
